Hooks 与 TypeScript
概述
在本教程中,我们将深入探讨如何在 React 项目中结合使用 Hooks 和 TypeScript。Hooks 是 React 16.8 引入的新特性,允许你在不编写类的情况下使用状态和其他 React 特性。TypeScript 则是一种静态类型检查器,可以为 JavaScript 添加类型注解,从而提高代码的可维护性和可读性。
开发环境搭建
在开始之前,确保你已经安装了 Node.js 和 TypeScript 编译器。你可以通过以下命令来安装 TypeScript:
npm install -g typescript
接下来,使用 Create React App 创建一个支持 TypeScript 的项目:
npx create-react-app my-app --template typescript
cd my-app
第一个 TypeScript 程序
在 src 目录下创建一个新的文件 App.tsx,并编写以下代码:
import React from 'react';
const App: React.FC = () => {
return (
<div>
<h1>Hello, TypeScript!</h1>
</div>
);
};
export default App;
这个简单的组件展示了如何在 TypeScript 中定义一个 React 函数组件。React.FC 是 React 函数组件的类型注解。
基本类型和变量声明
TypeScript 允许你为变量、函数参数和返回值添加类型注解。以下是一些基本类型的示例:
let count: number = 0;
let message: string = "Hello, TypeScript!";
let isActive: boolean = true;
let numbers: number[] = [1, 2, 3];
let user: { name: string, age: number } = { name: "Alice", age: 30 };
TypeScript 配置文件 (tsconfig.json)
tsconfig.json 是 TypeScript 项目的配置文件。你可以在项目根目录下创建一个 tsconfig.json 文件,并添加以下配置:
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"jsx": "react"
},
"include": ["src"]
}
基本类型详解
数字类型
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
字符串类型
let color: string = "blue";
color = 'red';
布尔类型
let isDone: boolean = false;
数组类型
let list: number[] = [1, 2, 3];
let list2: Array<number> = [1, 2, 3];
元组类型
let x: [string, number];
x = ["hello", 10];
枚举类型
枚举类型允许你定义一组命名的常量。
enum Color {
Red,
Green,
Blue
}
let c: Color = Color.Green;
Any 和 Unknown 类型
any 类型允许你绕过类型检查,而 unknown 类型则要求你在使用之前进行类型检查。
let notSure: any = 4;
notSure = "maybe a string instead";
let uncertain: unknown = 4;
if (typeof uncertain === "string") {
console.log(uncertain.length);
}
Void, Null 和 Undefined
void 表示没有返回值的函数,null 和 undefined 是所有类型的子类型。
function warnUser(): void {
console.log("This is my warning message");
}
let u: undefined = undefined;
let n: null = null;
Never 类型
never 类型表示永远不会发生的值。
function error(message: string): never {
throw new Error(message);
}
类型断言
类型断言允许你告诉编译器某个值的具体类型。
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
函数声明和表达式
你可以使用函数声明或函数表达式来定义函数。
function add(x: number, y: number): number {
return x + y;
}
let myAdd = function(x: number, y: number): number {
return x + y;
};
可选参数和默认参数
TypeScript 支持可选参数和默认参数。
function buildName(firstName: string, lastName?: string): string {
if (lastName) {
return firstName + " " + lastName;
} else {
return firstName;
}
}
function buildName2(firstName: string, lastName = "Smith"): string {
return firstName + " " + lastName;
}
剩余参数
剩余参数允许你将多个参数收集到一个数组中。
function buildName3(firstName: string, ...restOfName: string[]): string {
return firstName + " " + restOfName.join(" ");
}
函数重载
函数重载允许你定义多个函数签名。
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any): any {
return a + b;
}
This 和箭头函数
箭头函数不会改变 this 的指向。
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function() {
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
};
}
};
接口定义和使用
接口用于定义对象的形状。
interface LabeledValue {
label: string;
}
function printLabel(labeledObj: LabeledValue) {
console.log(labeledObj.label);
}
let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);
可选属性和只读属性
接口中的属性可以是可选的或只读的。
interface SquareConfig {
color?: string;
width?: number;
}
interface Point {
readonly x: number;
readonly y: number;
}
函数类型接口
接口可以描述函数类型。
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
let result = source.search(subString);
return result > -1;
};
类类型接口
接口可以描述类的结构。
interface ClockInterface {
currentTime: Date;
setTime(d: Date): void;
}
class Clock implements ClockInterface {
currentTime: Date = new Date();
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) {}
}
继承接口
接口可以继承其他接口。
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface Square extends Shape, PenStroke {
sideLength: number;
}
let square = {} as Square;
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;
类型别名 vs 接口
类型别名和接口都可以用来定义类型,但它们有一些区别。
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
interface Person {
name: string;
age: number;
}
类的定义和实例化
类是面向对象编程的基础。
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter("world");
继承和多态
类可以继承其他类,并实现多态。
class Animal {
move(distanceInMeters: number = 0) {
console.log(`Animal moved ${distanceInMeters}m.`);
}
}
class Dog extends Animal {
bark() {
console.log("Woof! Woof!");
}
}
const dog = new Dog();
dog.bark();
dog.move(10);
dog.bark();
访问修饰符
TypeScript 支持 public、private 和 protected 访问修饰符。
class Animal {
public name: string;
public constructor(theName: string) {
this.name = theName;
}
public move(distanceInMeters: number) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
静态属性和方法
静态属性和方法属于类本身,而不是类的实例。
class Grid {
static origin = { x: 0, y: 0 };
calculateDistanceFromOrigin(point: { x: number; y: number }) {
let xDist = point.x - Grid.origin.x;
let yDist = point.y - Grid.origin.y;
return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
}
constructor(public scale: number) {}
}
抽象类
抽象类不能被实例化,只能被继承。
abstract class Department {
constructor(public name: string) {}
printName(): void {
console.log("Department name: " + this.name);
}
abstract printMeeting(): void; // 必须在派生类中实现
}
class AccountingDepartment extends Department {
constructor() {
super("Accounting and Auditing"); // 在派生类的构造函数中必须调用 super()
}
printMeeting(): void {
console.log("The Accounting Department meets each Monday at 10am.");
}
generateReports(): void {
console.log("Generating accounting reports...");
}
}
交叉类型
交叉类型可以将多个类型合并为一个类型。
function extend<T, U>(first: T, second: U): T & U {
let result = <T & U>{};
for (let id in first) {
(<any>result)[id] = (<any>first)[id];
}
for (let id in second) {
if (!result.hasOwnProperty(id)) {
(<any>result)[id] = (<any>second)[id];
}
}
return result;
}
联合类型
联合类型表示一个值可以是多种类型之一。
function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
类型保护和类型区分
类型保护允许你在运行时检查变量的类型。
function isFish(pet: Fish | Bird): pet is Fish {
return (<Fish>pet).swim !== undefined;
}
可为 null 的类型
TypeScript 支持可为 null 的类型。
let s = "foo";
s = null; // 错误, 'null' 不能赋值给 'string'
let sn: string | null = "bar";
sn = null; // 可以
字面量类型
字面量类型允许你定义具体的值作为类型。
let x: "hello" = "hello";
x = "hello"; // 可以
x = "howdy"; // 错误
索引类型和映射类型
索引类型和映射类型允许你操作对象的键和值。
function pluck<T, K extends keyof T>(o: T, names: K[]): T[K][] {
return names.map(n => o[n]);
}
interface Person {
name: string;
age: number;
}
let person: Person = {
name: "Jarid",
age: 35
};
let strings: string[] = pluck(person, ["name"]); // 可以
泛型函数
泛型函数允许你编写可以处理多种类型的函数。
function identity<T>(arg: T): T {
return arg;
}
let output = identity<string>("myString");
泛型接口
泛型接口允许你定义可以处理多种类型的接口。
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
泛型类
泛型类允许你定义可以处理多种类型的类。
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) {
return x + y;
};
泛型约束
泛型约束允许你限制泛型参数的类型。
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // 现在我们知道它有 .length 属性,所以不再报错
return arg;
}
ES6 模块系统
ES6 模块系统允许你将代码组织成模块。
// math.ts
export function add(x: number, y: number): number {
return x + y;
}
// app.ts
import { add } from "./math";
console.log(add(1, 2)); // 3
命名空间
命名空间允许你将代码组织成逻辑分组。
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
const lettersRegexp = /^[A-Za-z]+$/;
const numberRegexp = /^[0-9]+$/;
export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}
export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
}
模块解析策略
TypeScript 支持不同的模块解析策略。
{
"compilerOptions": {
"moduleResolution": "node"
}
}
类装饰器
类装饰器允许你在类定义时添加元数据。
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class Greeter {
greeting